<?php
namespace Core\Db;
use Core\Server;

class Mysql
{
    /**
     * @var \PDO
     */
    private $db;

    private $statement;

    private $_fetchMode = 2;

    private $dsn;

    private $username;

    private $password;

    private $option;

    private $charset;

    private $timeout;

    private $connectTime;

    /**
     * Mysql constructor.
     * @param $host
     * @param $port
     * @param $username
     * @param $password
     * @param $dbname
     * @param $charset
     * @param $timeout
     */
    private function __construct($dsn, $username, $password, $charset,$option,$timeout)
    {
       $this->dsn = $dsn;
       $this->username = $username;
       $this->password = $password;
       $this->charset = $charset;
       $this->option = $option;
       $this->timeout = $timeout;
       $this->connect();
    }

    private function connect() {
        $this->db = new \PDO($this->dsn, $this->username, $this->password, $this->option);
        $this->db->query('SET NAMES ' . $this->charset);
        $this->connectTime = time();
    }

    /**
     * @param $key
     * @return Mysql
     */
    static public function &Instance($key)
    {
        /**
         * @var $conns Mysql[]
         */
        static $conns = [];
//        if(isset($conns[$key])) {
//            try{
//                $conns[$key]->db->getAttribute(PDO::ATTR_SERVER_INFO);
//            } catch (PDOException $e) {
//                if(strpos($e->getMessage(), 'MySQL server has gone away')!==false){
//                    $conns[$key]->close();
//                    unset($conns[$key]);
//                }
//            }
//        }
        if(!isset($conns[$key])) {
            $config = Server::Instance()->appConfig['db'][$key];
            $conns[$key] = new self($config['dsn'],$config['username'], $config['password'],$config['charset'],$config['option'],$config['timeout']);
        }else {
           if(time()- $conns[$key]->connectTime >= $conns[$key]->timeout ) {
               echo date('Y-m-d H:i:s')." reconnect {$conns[$key]->dsn} \r\n";
               $conns[$key]->close();
               $conns[$key]->connect();
           }
        }
        return $conns[$key];
    }

    /**
     * @param $sql
     * @param array $bind
     * @param null $fetchMode
     * @return mixed
     * @throws \Exception
     */
    public function fetchAll($sql, $bind = array(), $fetchMode = null)
    {
        if ($fetchMode === NULL) {
            $fetchMode = $this->_fetchMode;
        }

        $stmt = $this->query($sql, $bind);
        $res = $stmt->fetchAll($fetchMode);

        return $res;
    }

    public function fetchRow($sql, array $bind = array(), $fetchMode = null)
    {
        if ($fetchMode === null) {
            $fetchMode = $this->_fetchMode;
        }
        $stmt = $this->query($sql, $bind);
        $result = $stmt->fetch($fetchMode);
        return $result;
    }


    public function fetchOne($sql, array $bind = array())
    {
        $stmt = $this->query($sql, $bind);
        $res = $stmt->fetchColumn(0);
        return $res;
    }


    public function insert($table, array $bind)
    {
        $cols = array();
        $vals = array();

        foreach ($bind as $k => $v) {
            $cols[] = '`' . $k . '`';
            $vals[] = ':' . $k;
            unset($bind[$k]);
            $bind[':' . $k] = $v;
        }

        $sql = 'INSERT INTO '
            . $table
            . ' (' . implode(',', $cols) . ') '
            . 'VALUES (' . implode(',', $vals) . ')';

        $stmt = $this->query($sql, $bind);
        //$res = $stmt->rowCount();

        return true;
    }

    /**
     * @param $table
     * @param string $where
     * @return mixed
     * @throws \Exception
     */
    public function delete($table, $where = '')
    {
        $whereSql = '';
        $bind = [];
        if($where) {
            if (is_array($where)) {
                foreach ($where as $k => $v) {
                    $whereSql .= ' AND '.$k.'=:'.$k ;
                    $bind[':' . $k] = $v;
                }
                $whereSql = ' WHERE '.substr($whereSql,4);
            }else {
                $whereSql = ' WHERE '.$where;
            }
        }
        $sql = 'DELETE FROM '
            . $table
            . $whereSql;

        $stmt = $this->query($sql,$bind);

        $res = $stmt->rowCount();

        return $res;
    }

    public function updateAttr($table,$set,$where)  {
        $whereSql = '';
        $bind = [];
        if($where) {
            if (is_array($where)) {
                foreach ($where as $k => $v) {
                    $whereSql .= ' AND '.$k.'=:'.$k ;
                    $bind[':' . $k] = $v;
                }
                $whereSql = ' WHERE '.substr($whereSql,4);
            }else {
                $whereSql = ' WHERE '.$where;
            }
        }
        $sql = 'UPDATE '
            . $table
            . ' SET ' . $set
            . $whereSql;

        $stmt = $this->query($sql, $bind);
        if($stmt) {
            return $stmt->rowCount();
        }else {
            return false;
        }

    }

    /**
     * @param $table
     * @param array $bind
     * @param string $where
     * @return bool
     * @throws \Exception
     */
    public function update($table, array $bind, $where = '')
    {
        $set = array();

        foreach ($bind as $k => $v) {
            $bind[':up' . $k] = $v;
            $set[] = $k . '=' .  ':up' . $k;
            unset($bind[$k]);
        }

        $whereSql = '';
        if($where) {
            if (is_array($where)) {
                foreach ($where as $k => $v) {
                    $whereSql .= ' AND '.$k.'=:wh'.$k ;
                    $bind[':wh' . $k] = $v;
                }
                $whereSql = ' WHERE '.substr($whereSql,4);
            }else {
                $whereSql = ' WHERE '.$where;
            }
        }
        $sql = 'UPDATE '
            . $table
            . ' SET ' . implode(',', $set)
            . $whereSql;
        $stmt = $this->query($sql, $bind);
        //$res = $stmt->rowCount();
        if($stmt) {
            return $stmt->rowCount();
        }else {
            return false;
        }
    }

    /**
     * 获取新增ID
     *
     * @param string $tableName
     * @param string $primaryKey
     * @return string
     */
    public function lastInsertId()
    {
        return (string)$this->db->lastInsertId();
    }

    public function query($sql, $bind = array())
    {

        if (!is_array($bind)) {
            $bind = array($bind);
        }

        $stmt = $this->prepare($sql);

        if(!$stmt->execute($bind)) {
            $errInfo = $stmt->errorInfo();
            if($errInfo[1] == 2006 || $errInfo[1] == 2013) {
                $this->close();
                $this->connect();
                $stmt = $this->prepare($sql);
                if(!$stmt->execute($bind)) {
                    $errInfo = $stmt->errorInfo();
                    $err = 'stmt err:'.$errInfo[1].' :'.$errInfo[2].' sql:'.$sql.var_export($bind,true);
                    var_dump($err);
                    throw  new \Exception($err);
                }
            }else {
                $err = 'stmt err:'.$errInfo[1].' :'.$errInfo[2].' sql:'.$sql.var_export($bind,true);
                var_dump($err);
                throw  new \Exception($err);
            }
        }
        $stmt->setFetchMode($this->_fetchMode);
        return $stmt;


    }

    public function prepare($sql = '')
    {
        if (empty($sql)) {
            return false;
        }

        $this->statement = $this->db->prepare($sql);

        return $this->statement;
    }

    public function execute($param = '')
    {
        if (is_array($param)) {
            return $this->statement->execute($param);
        } else {
            return $this->statement->execute();
        }
    }


    public function transaction($callback,&$err=null) {
        if($this->db->beginTransaction()) {
            try{
                $callback($this->db);
                return $this->db->commit();
            }catch (\Exception $e) {
                $this->db->rollBack();
                $err = $e->getMessage();
                return false;
            }
        }else {
            throw  new \Exception('beginTransaction err');
        }
    }


    /**
     * 关闭数据库操作
     */
    public function close()
    {
        $this->_db = null;
    }
}